下半部分,主要考量了全新的异步模式 Promise 和 Generator 以及它们的语法糖 async。当然,还有超级好用的的模块实现,以及让人头大的的类,这里建议配合《你不知道的JavaScript(中)》的第二部分反复阅读,理解 JavaScript 强大的异步实现

ECMAScript6入门

Promise 对象

Promise 是异步编程的一种解决方案,比传统的解决方案——回调函数和事件——更合理和更强大

  • 两个特点
    • Promise 对象有三种状态:Pending、Resolved 和 Rejected。只有异步操作的结果,可以决定当前是哪一种状态,任何其他操作都无法改变这个状态
    • 一旦状态改变,就不会再变,任何时候都可以得到这个结果
    • 无法取消 Promise,一旦新建它就会立即执行,无法中途取消
    • 如果不设置回调函数,Promise 内部抛出的错误,不会反应到外部
    • 处于 Pending 状态时,无法得知目前进展到哪一个阶段
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var promise = new Promise(resolve, reject) {
// promise 这里会立即执行
if (true) { // 异步成功
resolve(value) // 把 value 作为参数传出去
} else {
reject(new Error('报错啦')) // 通常是 Error 对象的实例
}
}
// then 方法的第二个参数一般不建议使用,使用 .catch() 替代,使代码更优雅可读,同时可捕捉回调函数中抛出的错误
promise.then(function (value) {
// 处理结果
}).catch(function (error) {
// 相当于 .then(null, function (error) {..})
// 处理错误
})
  • resolve 的参数可以也可是一个 promise 实例

    1
    2
    3
    4
    5
    6
    7
    var p1 = new Promise(function (resolve, reject) {
    // ...
    })
    var p2 = new Promise(function (resolve, reject) {
    // ...
    resolve(p1) // 在这里,p1 的状态就传给了 p2,会等待 p1 的状态决议后才执行
    })
  • Promise.prototype.then()

    本方法接受两个参数 resolvereject(但不建议使用第二个参数,而是使用 .catch() 方法替代),返回一个新的 Promise 实例(不是原来那个),因此可以使用链式继续调用 .then() 方法

1
2
3
4
5
getJSON("/posts.json").then(function(json) {
return json.post // 这里返回的结果作为参数传给下一个 then 调用
}).then(function(post) {
// 如果 post 是另外一个 promise,则会等待该 promise 决议
})
  • Promise.all() 方法用于多个 promise 实例

    • 全部 promise 都变成 resolve,才返回 resolve
    • 只要有一个 reject,返回第一个 reject 那个
  • Promise.race() 返回最先被决议的那个 promise

  • Promise.resolve() 方法会将一个普通对象转换成 Promise 对象
    1
    2
    3
    Promise.resolve('foo')
    // 等价于
    new Promise(resolve => resolve('foo'))

Iterator 和 for…of 循环

Iterator 的作用有三个:一是为各种数据结构,提供一个统一的、简便的访问接口;二是使得数据结构的成员能够按某种次序排列;三是 ES6 创造了一种新的遍历命令 for...of 循环,Iterator 接口主要供 for...of 消费

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
var it = makeIterator(['a', 'b']);
it.next() // { value: "a", done: false }
it.next() // { value: "b", done: false }
it.next() // { value: undefined, done: true }
// 手动模拟一个迭代器
function makeIterator(array) {
var nextIndex = 0;
return {
next: function() {
return nextIndex < array.length ?
{value: array[nextIndex++], done: false} :
{value: undefined, done: true};
}
};
}
  • 在ES6中,有三类数据结构原生具备 Iterator 接口:数组、某些类似数组的对象、Set 和 Map 结构

Generator

用 Generator 函数,返回一个遍历器对象,代表 Generator 函数的内部指针。以后,每次调用遍历器对象的 next 方法,就会返回一个有着 valuedone 两个属性的对象。value 属性表示当前的内部状态的值,是 yield 语句后面那个表达式的值;done属性是一个布尔值,表示是否遍历结束

  • Generator 函数本质上还是函数,但这个函数可以分段执行返回
  • Generator 函数可以不用 yield 语句,这时就变成了一个单纯的暂缓执行函数。但 yield 语句只能用在 Generator 函数中
  • Generator 函数是 ES6 新增的底层实现,跟 Promise 不同
  • 使普通对象具备迭代器接口

    1
    2
    3
    4
    5
    6
    7
    8
    var myIterable = {};
    myIterable[Symbol.iterator] = function* () {
    yield 1;
    yield 2;
    yield 3;
    };
    //
    [...myIterable] // [1, 2, 3]
  • yield 句本身没有返回值,或者说总是返回 undefinednext 方法可以带一个参数,该参数就会被当作上一个 yield 语句的返回值

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function* f() {
    for(var i = 0; true; i++) {
    var reset = yield i;
    if(reset) { i = -1; }
    }
    }
    // 可以在 Generator 函数运行的不同阶段,从外部向内部注入不同的值,从而调整函数行为
    var g = f();
    g.next() // { value: 0, done: false }
    g.next() // { value: 1, done: false }
    g.next(true) // { value: 0, done: false }
  • for...of 循环可以自动遍历 Generator 函数时生成的 Iterator 对象,且此时不再需要调用 next 方法

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    function *foo() {
    yield 1;
    yield 2;
    yield 3;
    yield 4;
    yield 5;
    return 6;
    }
    for (let v of foo()) {
    console.log(v);
    }
    // 1 2 3 4 5 不包含返回对象
  • 配合 try...catchGenerator.throw() 方法,可以方便地根据需要在函数内外捕获错误

  • Generator.return() 方法可以返回值并停止迭代
  • yield*后面的 Generator 函数(没有 return 语句时),不过是 for...of 的一种简写形式,完全可以用后者替代前者。反之,则需要用 var value = yield* iterator 的形式获取 return 语句的值
  • 任何数据结构只要有 Iterator 接口,就可以被 yield* 遍历
  • 其返回的 Promise 对象的只在所有异步状态都完成后才决议(除非提前 return 或抛出错误),非常类似 Promise.all
  • 使用 Generator 包装一个对象
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    function* objectEntries(obj) {
    let propKeys = Reflect.ownKeys(obj)
    for (let propKey of propKeys) {
    yield [propKey, obj[propKey]]
    }
    }
    let jane = { first: 'Jane', last: 'Doe' }
    for (let [key, value] of objectEntries(jane)) {
    console.log(`${key}: ${value}`)
    }

async 函数(ES2017)

是 Generator 函数的语法糖

  • 自带执行器,因此不用手动调用 next(),可以看作是多个异步操作包装成的 Promise 对象,因此下一步通常是调用 then 方法进行处理,本质上等于把 Generator 和它的执行期封装在一个函数中
  • 具备五种定义方法
    • 函数声明
    • 函数表达式
    • 对象方法
    • 箭头函数
  • await 命令后面一定是一个 Promise 对象(自动包装)
  • 为了能方便捕获错误,最好讲 await 操作放在 try...catch
    1
    2
    3
    4
    5
    6
    7
    async function myFunction() {
    try {
    await somethingThatReturnsAPromise()
    } catch (err) {
    console.log(err)
    }
    }

Class

  • 基本应用

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    // ES5
    function Person(name, age) {
    this.name = name
    this.age = age
    }
    Person.prototype.getName = function () {
    return this.name
    }
    // ES6
    class Person {
    constructor (name, age) {
    this.name = name
    this.age = age
    }
    //
    getName () {
    return this.name
    }
    }
  • Class 必须用 new 调用,否则报错,同时不存在变量提升

  • 与ES5不同的地方是,Class 定义的方法是不可枚举的
  • constructor 方法是类的默认方法,如果不指定,则默认添加一个空方法
  • Class 中默认就是严格模式
  • 类之间通过 extends 关键字实现继承
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    // ColorPoint 类,该类通过 extends 关键字,继承了 Point 类的所有属性和方法
    class ColorPoint extends Point {} // 这里相当于复制
    //
    class ColorPoint extends Point {
    // 子类必须在 constructor 方法中调用 super 方法,否则新建实例时会报错。这是因为子类没有自己的 this 对象,而是继承父类的 this 对象,然后对其进行加工
    constructor(x, y, color) {
    super(x, y); // 调用父类的 constructor(x, y)
    this.color = color;
    }
    toString() {
    return this.color + ' ' + super.toString(); // 调用父类的 toString()
    }
    }

Module

  • CommonJS 与 ES6 的区别
    • CommonJS 先生成对象再读取其中的方法,运行时加载,无法静态优化;ES6 模块不是对象,编译时加载,可实现静态优化
    • CommonJS 输出的是值的缓存,不存在动态更新;ES6 模块输出的是值的引用
  • ES6 中的模块自动采用严格模式
  • export 命令,输出模块的变量(否则外部无法读取),该命令不能出现在块作用域中
  • import 命令加载模块并读取其中变量,该命令有提升作用,自动提升到整个模块头部优先执行,该命令不能出现在块作用域中
  • import 是静态执行的,所以不能使用表达式和变量这些运行时语法,该命令是 Singleton 模式
  • 使用默认输出时,import语句无需大括号
  • 浏览器中使用模块,该模块默认为延迟脚本
    1
    <script type="module" src="foo.js"></script>